package com.hjw.httpclient.async.util;

import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.client.HttpAsyncClient;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;

import java.net.URISyntaxException;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 异步HttpClient的工具类
 *
 * @author hjwStrart
 * @time 2022-11-30 20:41
 * @version: 1.0
 */
public class HttpClientUtils {

    private static final PoolingNHttpClientConnectionManager ncm;
    private static final RequestConfig requestConfig;
    private static volatile CloseableHttpAsyncClient httpAsyncClient;

    /**
     * 初始化参数
     */
    static {
        requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(3000)
                .setConnectionRequestTimeout(1000)
                .build();
        IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                .setIoThreadCount(Runtime.getRuntime().availableProcessors() * 2 + 1)
                .setSoKeepAlive(true)
                .build();
        ConnectingIOReactor connectingIOReactor = null;
        try {
            connectingIOReactor = new DefaultConnectingIOReactor(ioReactorConfig);
        } catch (IOReactorException e) {
            e.printStackTrace();
        }
        assert connectingIOReactor != null;
        ncm = new PoolingNHttpClientConnectionManager(connectingIOReactor);
        ncm.setMaxTotal(1000);
        ncm.setDefaultMaxPerRoute(200);

        // 创建一个调度线程，定时检测线程池，关闭过期和长时间空间的连接
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                ncm.closeExpiredConnections();// 关闭过期连接
                // 关闭空闲了 10s 的连接（同时会关闭过期连接）
                ncm.closeIdleConnections(10, TimeUnit.SECONDS);
            }
        }, 5L, 10L, TimeUnit.SECONDS);

    }

    /**
     * 双重锁机制，获取异步HttpClient
     *
     * @return
     */
    private static CloseableHttpAsyncClient getHttpAsyncClient() {
        if (httpAsyncClient == null) {
            synchronized (HttpClientUtils.class) {
                if (httpAsyncClient == null) {
                    httpAsyncClient = HttpAsyncClients.custom()
                            .setConnectionManager(ncm)
                            .setDefaultRequestConfig(requestConfig)
                            .build();
                    httpAsyncClient.start();
                }
            }
        }
        return httpAsyncClient;
    }

    /**
     * 使用 URIBuilder构建url，可以不用对url进行一次 UriEncoder 编码
     *
     * @param urlStr
     * @return
     * @throws URISyntaxException
     */
    public static String getUrl(String urlStr) {
        try {
            return new URIBuilder(urlStr).toString();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取 HttpUriRequest（HttpGet） 对象
     *
     * @param urlStr
     * @return
     * @throws URISyntaxException
     */
    public static HttpUriRequest getHttpRequest(String urlStr){
        return new HttpGet(getUrl(urlStr));
    }

    public static Future<HttpResponse> doGet(String urlStr, FutureCallback<HttpResponse> callback) {
        HttpGet httpGet = new HttpGet(getUrl(urlStr));
        if (httpAsyncClient == null) {
            httpAsyncClient = getHttpAsyncClient();
        }
        return httpAsyncClient.execute(httpGet, callback);
    }

    public static Future<HttpResponse> doGet(HttpUriRequest httpRequest, FutureCallback<HttpResponse> callback) {
        if (httpAsyncClient == null) {
            httpAsyncClient = getHttpAsyncClient();
        }
        return httpAsyncClient.execute(httpRequest, callback);
    }

}
